跳到主要内容

gRPC 的认证

gRPC 默认提供了两种认证方式:

  • 基于SSL/TLS认证方式
  • 远程调用认证方式

两种方式可以混合使用

证书制作

以下实现 TLS 认证机制

首先是制作私钥 (.key)

# 使用 RSA 算法生成私钥
openssl genrsa -out server.key 2048
# or
# 使用 ECDSA 算法生成私钥
openssl ecparam -genkey -name secp384r1 -out server.key
提示

RSA 和 ECDSA 是最广泛使用的数字签名算法

RSA 是目前计算机密码学中最经典算法,也是目前为止使用最广泛的数字签名算法,RSA 数字签名算法的密钥实现与 RSA 的加密算法是一样的,算法的名称都叫 RSA。密钥的产生和转换都是一样的,包括在售的所有 SSL 数字证书、代码签名证书、文档签名以及邮件签名大多都采用 RSA 算法进行加密。

ECDSA:Elliptic Curve Digital Signature Algorithm(椭圆曲线签名算法)也是一种常用的签名算法,它的特点是可以从私钥推出公钥。比特币的签名算法就采用了 ECDSA 算法,使用标准椭圆曲线 secp256k1。

ECC 与 RSA 相比,有以下的优点: a. 相同密钥长度下,安全性能更高,如160位ECC已经与1024位RSA、DSA有相同的安全强度。 b. 计算量小,处理速度快,在私钥的处理速度上(解密和签名),ECC远 比RSA、DSA快得多。 c. 存储空间占用小 ECC的密钥尺寸和系统参数与RSA、DSA相比要小得多, 所以占用的存储空间小得多。 d. 带宽要求低使得ECC具有广泛得应用前景。

更多细节参考 数字签名算法介绍和区别

然后是对上面生成的私钥生成证书

openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650

自定义信息

-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:XxXx
Locality Name (eg, city) []:XxXx
Organization Name (eg, company) [Internet Widgits Pty Ltd]:XX Co. Ltd
Organizational Unit Name (eg, section) []:Dev
Common Name (e.g. server FQDN or YOUR name) []:server name
Email Address []:xxx@xxx.com

续上:grpc 中 TLS 认证证书问题

golang 1.15+ 版本上,用 gRPC 通过 TLS 实现数据传输加密时,会报错证书的问题

panic: rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate
is not valid for any names, but wanted to match localhost"

造成的原因是因为我们用的证书,并没有开启SAN扩展(默认是没有开启SAN扩展)所生成的,

导致客户端和服务端无法建立连接

使用开启扩展 SAN 的证书

提示

什么是 SAN?

SAN(Subject Alternative Name) 是 SSL 标准 x509 中定义的一个扩展。使用了 SAN 字段的 SSL 证书,可以扩展此证书支持的域名,使得一个证书可以支持多个不同域名的解析。

生成 CA 根证书

新建 ca.conf

写入下列内容

[ req ]
default_bits = 4096
distinguished_name = req_distinguished_name

[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = CN
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = GuangDong
localityName = Locality Name (eg, city)
localityName_default = Shenzhen
organizationName = Organization Name (eg, company)
organizationName_default = Sheld
commonName = Common Name (e.g. server FQDN or YOUR name)
commonName_max = 64
commonName_default = Ted CA Test

再去生成 ca 秘钥,得到 ca.key

openssl genrsa -out ca.key 4096

生成 ca 证书签发请求,得到 ca.csr

openssl req \
-new \
-sha256 \
-out ca.csr \
-key ca.key \
-config ca.conf

然后一路回车就行了

最后生成 ca 根证书,得到 ca.crt

openssl x509 \
-req \
-days 3650 \
-in ca.csr \
-signkey ca.key \
-out ca.crt

生成终端用户证书

准备配置文件,得到 server.conf

写入内容如下:

[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = req_ext

[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = CN
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = GuangDong
localityName = Locality Name (eg, city)
localityName_default = GuangZhou
organizationName = Organization Name (eg, company)
organizationName_default = Sheld
commonName = alsritter
commonName_max = 64
# commonName_default 这个配置项,如果有域名就写域名,本地就 localhost
commonName_default = alsritter.icu

[ req_ext ]
subjectAltName = @alt_names

[alt_names]
DNS.1 = alsritter.icu
IP = 127.0.0.1

生成秘钥,得到 server.key

openssl genrsa -out server.key 2048

生成证书签发请求,得到 server.csr

openssl req \
-new \
-sha256 \
-out server.csr \
-key server.key \
-config server.conf

shell 交互时一路回车就行

用 CA 证书生成终端用户证书,得到 server.crt

openssl x509 \
-req \
-days 3650 \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-in server.csr \
-out server.pem \
-extensions req_ext \
-extfile server.conf

现在证书已经生成完毕, server.pem 和 server.key 就是我们需要的证书和密钥

服务端代码:

creds, err := credentials.NewServerTLSFromFile("./keys/server.pem", "./keys/server.key")

客户端代码:

creds, err := credentials.NewClientTLSFromFile("./keys/server.pem", "alsritter.icu")

初始化环境

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/golang/protobuf/protoc-gen-go;

创建 proto/helloworld.proto 文件

syntax = "proto3"; // 指定proto版本
package hello; // 指定包名

option go_package = "./proto"; // 这个路径必须包含一个 '.' 或者 '/'

// 定义Hello服务
service Hello {
// 定义SayHello方法
rpc SayHello(HelloRequest) returns (HelloResponse) {}
}

// HelloRequest 请求结构
message HelloRequest {
string name = 1;
}

// HelloResponse 响应结构
message HelloResponse {
string message = 1;
}

编译生成 go 文件

protoc -I . --go_out=plugins=grpc:. proto/*.proto

编写服务端代码

server/main.go

package main

import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
"net"

pb "grpc-auth/proto"
)

const (
// Address gRPC 服务地址
Address = "127.0.0.1:50052"
)

// 定义helloService并实现约定的接口
type helloService struct{}

var HelloService = helloService{}

func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
resp := new(pb.HelloResponse)
resp.Message = "Hello " + in.Name + "."

return resp, nil
}

func main() {
listen, err := net.Listen("tcp", Address)
if err != nil {
grpclog.Fatalf("failed to listen: %v", err)
}

// TLS 认证
creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key")
if err != nil {
grpclog.Fatalf("Failed to generate credentials %v", err)
}

// 实例化grpc Server, 并开启TLS认证
s := grpc.NewServer(grpc.Creds(creds))

// 注册HelloService
pb.RegisterHelloServer(s, HelloService)

fmt.Println("Listen on " + Address + " with TLS")
s.Serve(listen)
}

服务端在实例化 grpc Server 时,可配置多种选项,TLS 认证是其中之一

客户端添加TLS认证

client/main.go

package main

import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
pb "grpc-auth/proto"
)

const (
// Address gRPC服务地址
Address = "127.0.0.1:50052"
)

func main() {
// TLS连接
creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "alsritter.icu")
if err != nil {
grpclog.Fatalf("Failed to create TLS credentials %v", err)
}
conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds))

if err != nil {
grpclog.Fatalln(err)
}

defer conn.Close()

// 初始化客户端
c := pb.NewHelloClient(conn)

// 调用方法
reqBody := new(pb.HelloRequest)
reqBody.Name = "gRPC"
r, err := c.SayHello(context.Background(), reqBody)

if err != nil {
grpclog.Fatalln(err)
}

fmt.Println(r.Message)
}

References